---
title: Middleware
description: Learn how to use middleware in Astro.
i18nReady: true
---
import PackageManagerTabs from '~/components/tabs/PackageManagerTabs.astro';

**Middleware** allows you to intercept requests and responses and inject behaviors dynamically every time a page or endpoint is about to be rendered. This rendering occurs at build time for all prerendered pages, but occurs when the route is requested for pages rendered on demand.

Middleware also allows you to set and share request-specific information across endpoints and pages by mutating a `locals` object that is available in all Astro components and API endpoints. This object is available even when this middleware runs at build time.

## Basic Usage

1. Create `src/middleware.js|ts` (Alternatively, you can create `src/middleware/index.js|ts`.)

2. Inside this file, export an [`onRequest()`](/en/reference/api-reference/#onrequest) function that can be passed a [`context` object](#the-context-object) and `next()` function. This must not be a default export.

    ```js title="src/middleware.js"
    export function onRequest ({ locals, request }, next) {
        // intercept data from a request
        // optionally, modify the properties in `locals`
        locals.title = "New title";

        // return a Response or the result of calling `next()`
        return next();
    };
    ```

3. Inside any `.astro` file, access response data using `Astro.locals`.

    ```astro title="src/components/Component.astro"
    ---
    const data = Astro.locals;
    ---
    <h1>{data.title}</h1>
    <p>This {data.property} is from middleware.</p>
    ```

### The `context` object

The [`context`](/en/reference/api-reference/#endpoint-context) object includes information to be made available to other middleware, API routes and `.astro` routes during the rendering process.

This is an optional argument passed to `onRequest()` that may contain the `locals` object as well as any additional properties to be shared during rendering. For example, the `context` object may include cookies used in authentication.

### Storing data in `context.locals`

`context.locals` is an object that can be manipulated inside the middleware.

This `locals` object is forwarded across the request handling process and is available as a property to [`APIContext`](/en/reference/api-reference/#contextlocals) and [`AstroGlobal`](/en/reference/api-reference/#astrolocals). This allows data to be shared between middlewares, API routes, and `.astro` pages. This is useful for storing request-specific data, such as user data, across the rendering step.

:::tip[Integration properties]
[Integrations](/en/guides/integrations-guide/) may set properties and provide functionality through the `locals` object. If you are using an integration, check its documentation to ensure you are not overriding any of its properties or doing unnecessary work.
:::

You can store any type of data inside `locals`: strings, numbers, and even complex data types such as functions and maps.

```js title="src/middleware.js"
export function onRequest ({ locals, request }, next) {
    // intercept data from a request
    // optionally, modify the properties in `locals`
    locals.user.name = "John Wick";
    locals.welcomeTitle = () => {
        return "Welcome back " + locals.user.name;
    };

    // return a Response or the result of calling `next()`
    return next();
};
```

Then you can use this information inside any `.astro` file with `Astro.locals`.

```astro title="src/pages/orders.astro"
---
const title = Astro.locals.welcomeTitle();
const orders = Array.from(Astro.locals.orders.entries());
---
<h1>{title}</h1>
<p>This {data.property} is from middleware.</p>
<ul>
    {orders.map(order => {
        return <li>{/* do something with each order */}</li>;
    })}
</ul>
```

`locals` is an object that lives and dies within a single Astro route; when your route page is rendered, `locals` won't exist anymore and a new one will be created. Information that needs to persist across multiple page requests must be stored elsewhere.

:::note
The value of `locals` cannot be overridden at run time. Doing so would risk wiping out all the information stored by the user.  In `dev` mode, Astro performs checks and will throw an error if `locals` are overridden.
:::

## Example: redacting sensitive information

The example below uses middleware to replace "PRIVATE INFO" with the word "REDACTED" to allow you to render modified HTML on your page:

```js title="src/middleware.js"
export const onRequest = async (context, next) => {
    const response = await next();
    const html = await response.text();
    const redactedHtml = html.replaceAll("PRIVATE INFO", "REDACTED");
    
    return new Response(redactedHtml, {
        status: 200,
        headers: response.headers
    });
};
```

## Middleware types

You can import and use the utility function `defineMiddleware()` to take advantage of type safety:

```ts
// src/middleware.ts
import { defineMiddleware } from "astro:middleware";

// `context` and `next` are automatically typed
export const onRequest = defineMiddleware((context, next) => {

});
```

Instead, if you're using JsDoc to take advantage of type safety, you can use `MiddlewareHandler`:

```js
// src/middleware.js
/**
 * @type {import("astro").MiddlewareHandler}
 */
// `context` and `next` are automatically typed
export const onRequest = (context, next) => {

};
```

To type the information inside `Astro.locals`, which gives you autocompletion inside `.astro` files and middleware code, declare a global namespace in the `env.d.ts` file:

```ts title="src/env.d.ts"
/// <reference types="astro/client" />
declare namespace App {
    interface Locals {
        user: {
            name: string
        },
        welcomeTitle: () => string,
        orders: Map<string, object>
    }
}
```

Then, inside the middleware file, you can take advantage of autocompletion and type safety.

## Chaining middleware

Multiple middlewares can be joined in a specified order using [`sequence()`](/en/reference/api-reference/#sequence):

```js title="src/middleware.js"
import { sequence } from "astro:middleware";

async function validation(_, next) {
    console.log("validation request");
    const response = await next();
    console.log("validation response");
    return response;
}

async function auth(_, next) {
    console.log("auth request");
    const response = await next();
    console.log("auth response");
    return response;
}

async function greeting(_, next) {
    console.log("greeting request");
    const response = await next();
    console.log("greeting response");
    return response;
}

export const onRequest = sequence(validation, auth, greeting);
```

This will result in the following console order:

```sh
validation request
auth request
greeting request
greeting response
auth response
validation response
```

## Error pages

Middleware will attempt to run for all on-demand rendered pages, even when a matching route cannot be found. This includes Astro's default (blank) 404 page and any custom 404 pages. However, it is up to the [adapter](/en/guides/server-side-rendering/) to decide whether that code runs. Some adapters may serve a platform-specific error page instead.

Middleware will also attempt to run before serving a 500 error page, including a custom 500 page, unless the server error occured in the execution of the middleware itself. If your middleware does not run successfully, then you will not have access to `Astro.locals` to render your 500 page.
